#include <jni.h>
#include <surfaceflinger/Surface.h>
#include <surfaceflinger/ISurface.h>
#include "com_mirror_surface.h"

#include "com_mirror_aac.h"
#include "com_mirror_h264.h"
//#include "mediaPlayerListener.h"

#define TAG "JPEG_SKIA_DECODE"
//#define PROPERTY_VALUE_MAX
#define JAVA_JNI_CALLED_CLASS "com/hisilicon/multiscreen/mirror/MirrorView"

using namespace android;

struct fields_t
{
    jfieldID    context;
    jmethodID   write_pcm;
    jmethodID   config_audioTrack;
};

fields_t fields;
static JNIEnv* JniEnv = NULL;
static JavaVM* sVm;

/*
 * Throw an exception with the specified class and an optional message.
 */
int jniThrowException(JNIEnv* env, const char* className, const char* msg)
{
    jclass exceptionClass = env->FindClass(className);
    if (exceptionClass == NULL)
    {
        __android_log_print(ANDROID_LOG_ERROR,
                            TAG,
                            "Unable to find exception class %s",
                            className);
        return -1;
    }

    if (env->ThrowNew(exceptionClass, msg) != JNI_OK)
    {
        __android_log_print(ANDROID_LOG_ERROR,
                            TAG,
                            "Failed throwing '%s' '%s'",
                            className, msg);
    }
    return 0;
}

//get jni env
JNIEnv* getJNIEnv()
{
    JNIEnv* env = NULL;
    jint getEnvSuccess = sVm->GetEnv( (void**) &env, JNI_VERSION_1_4);
    if (getEnvSuccess == JNI_OK)
    {
        // __android_log_print(ANDROID_LOG_ERROR,TAG,"obtain JNIEnv OK");
        return env;
    }
    else if (getEnvSuccess == JNI_EDETACHED)
    {
        // __android_log_print(ANDROID_LOG_ERROR,TAG,"AttachCurrentThread");
        jint attachSuccess = sVm->AttachCurrentThread(&env, NULL);
        if (attachSuccess == 0)
        {
            // __android_log_print(ANDROID_LOG_ERROR,TAG,"AttachCurrentThread OK");
            return env;
        }
        else
        {
            __android_log_print(ANDROID_LOG_ERROR, TAG, "AttachCurrentThread failed");
            return NULL;
        }
    }
    else
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "obtain JNIEnv failed");
        return NULL;
    }
}

bool detachThreadFromJNI()
{
    JNIEnv* env = NULL;
    jint getEnvSuccess = sVm->GetEnv( (void**) &env, JNI_VERSION_1_4);
    if (getEnvSuccess == JNI_OK)
    {
        sVm->DetachCurrentThread();
        return true;
    }
    else
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "detachThreadFromJNI: obtain JNIEnv failed");
        return false;
    }
}

/*Android Audio Track ¿ØÖÆÀà£¬°üÀ¨ÅäÖÃAudioTrack ÊôÐÔ£¬ºÍÐ´ÈëPCMÒôÆµÊý¾Ý*/
/*Android Audio Track control class, include the attribute of AudioTrack Configration, and writting in PCM audio data*/
class JNIWritePCM : public MediaPlayerPCMWriter
{
public:
    JNIWritePCM(JNIEnv* env, jobject thiz, jobject weak_thiz);
    ~JNIWritePCM();
    void writePCM(unsigned char* buf, int bufCount);
    void detach();
    int configAudioTrack(int streamType, int sampleRate, int channelConfig, int bytesPerSample, int trackMode );

private:
    JNIWritePCM();
    jclass      mClass;     // Reference to MediaPlayer class
    jobject     mObject;    // Weak ref to MediaPlayer Java object to call on

};

//write pcm to audioTrack
JNIWritePCM::JNIWritePCM(JNIEnv* env, jobject thiz, jobject weak_thiz)
{
    // Hold onto the MediaPlayer class for use in calling the static method
    // that posts events to the application thread.
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL)
    {
        jniThrowException(env, "java/lang/Exception", JAVA_JNI_CALLED_CLASS);
        return;
    }
    mClass = (jclass)env->NewGlobalRef(clazz);

    // We use a weak reference so the MediaPlayer object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    mObject  = env->NewGlobalRef(weak_thiz);
}

JNIWritePCM::~JNIWritePCM()
{
    // remove global references
    JNIEnv* env = getJNIEnv();
    env->DeleteGlobalRef(mObject);
    env->DeleteGlobalRef(mClass);
}

//config AudioTrack param
int JNIWritePCM::configAudioTrack(int streamType, int sampleRate, int channelConfig, int bytesPerSample, int trackMode )
{
    __android_log_print( ANDROID_LOG_INFO, TAG,
                         "setting audio track:streamType %d,sampleRate %d,channels %d,format %d,trackMode %d",
                         streamType, sampleRate, channelConfig, bytesPerSample, trackMode);
    JNIEnv* env = getJNIEnv();
    if (env == NULL)
    { env = JniEnv; }
    return env->CallStaticIntMethod(mClass, fields.config_audioTrack, streamType, sampleRate, channelConfig, bytesPerSample, trackMode);
}

//write pcm data to audioTrack implement play audio
void JNIWritePCM::writePCM(unsigned char* buf, int bufCount)
{
    //    MMLOGI( TAG, "start writePCM");
    JNIEnv* env = getJNIEnv();
    if (env == NULL)
    { env = JniEnv; }
    if (buf == NULL)
    {
        detachThreadFromJNI();
        __android_log_print( ANDROID_LOG_INFO,  TAG,
                             "Detach current thread for thread end!!\n");
        return;
    }
    jbyteArray array = env->NewByteArray( bufCount);
    env->SetByteArrayRegion(array, 0, bufCount, (jbyte*)buf);
    env->CallStaticVoidMethod(mClass, fields.write_pcm, array);
    env->DeleteLocalRef(array);
}

void JNIWritePCM::detach()
{
    detachThreadFromJNI();
}

//config audio play or stop
static void native_configAudio(JNIEnv* env, jobject object, jint playFlag)
{
    HI_NativeAAC_Config(playFlag);
}

//start jpeg surface
static void native_setImgDisplaySurfaceEnable(JNIEnv* env, jobject thiz, jobject jsurface, jint version, jstring ip)
{

    HI_NativeSurface_SetEnable(env, jsurface, version, ip);

}

//destroy jpeg surface
static void native_DestroySurfaceNative(JNIEnv* env, jobject thiz)
{
    HI_NativeSurface_DeInit(NULL);
    HI_NativeAAC_DeInit(NULL);
}

#if defined (ANDROID_VERSION_23)

//start h264 surface
static void native_setH264SurfaceEnable(JNIEnv* env, jobject thiz, jobject jsurface, jint version, jstring ip)
{
   //for android23 not support h264
   HI_NativeSurface_SetEnable(env, jsurface, version, ip);
}

//detroy h264 surface
static void native_DestroyH264Surface(JNIEnv* env, jobject thiz)
{
    //for android23 not support h264
    HI_NativeSurface_DeInit(NULL);
    HI_NativeAAC_DeInit(NULL);
}

static void native_player_setup(JNIEnv* env, jobject thiz, jobject weak_this)
{
    jclass clazz;
    clazz = env->FindClass(JAVA_JNI_CALLED_CLASS);
    if (clazz == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaPlayer");
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaPlayer.mNativeContext");
        return;
    }
}

#else

static MirrorPlayer* getMediaPlayer(JNIEnv* env, jobject thiz)
{
    return (MirrorPlayer*)env->GetIntField(thiz, fields.context);
}

static MirrorPlayer* setMediaPlayer(JNIEnv* env, jobject thiz, MirrorPlayer* player)
{
    MirrorPlayer* prePlayer = (MirrorPlayer*)env->GetIntField(thiz, fields.context);
    if (prePlayer != NULL)
    {
        MMLOGD( TAG, "free pre mediaplayer object");
        free(prePlayer);
    }
    //save new player pointer to fields
    env->SetIntField(thiz, fields.context, (int)player);
    return prePlayer;
}
static void native_player_setup(JNIEnv* env, jobject thiz, jobject weak_this)
{
    jclass clazz;
    clazz = env->FindClass(JAVA_JNI_CALLED_CLASS);
    if (clazz == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find android/media/MediaPlayer");
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MediaPlayer.mNativeContext");
        return;
    }

    MirrorPlayer* mp = new MirrorPlayer();
    MMLOGI( TAG, "new MirrorPlayer()");
    if (mp == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
        return;
    }
    // Store our new C++ MediaPlayer in an opaque field in the Java object.
    setMediaPlayer(env, thiz, mp);
    MMLOGI( TAG, "setMediaPlayer");
}

//start h264 surface
static void native_setH264SurfaceEnable(JNIEnv* env, jobject thiz, jobject jsurface, jint version, jstring ip)
{
    MirrorPlayer* mp = getMediaPlayer(env, thiz);
    if (mp == NULL )
    {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    HI_NativeH264Surface_SetEnable(env, jsurface, mp);
}

//detroy h264 surface
static void native_DestroyH264Surface(JNIEnv* env, jobject thiz)
{
    MirrorPlayer* mp = getMediaPlayer(env, thiz);
    if (mp == NULL )
    {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    HI_NativeH264Surface_DeInit(env, mp);
    HI_NativeAAC_DeInit(NULL);
}

#endif

//play audio
static void native_setPCMCallBackEnable(JNIEnv* env, jobject thiz, jobject weak_this)
{
    jclass clazz;
    clazz = env->FindClass(JAVA_JNI_CALLED_CLASS);

    if (clazz == NULL)
    {
        jniThrowException(env, "java/lang/Exception", JAVA_JNI_CALLED_CLASS);
        return;
    }

    fields.write_pcm = env->GetStaticMethodID(clazz, "writePCM",
                       "([B)V");
    fields.config_audioTrack = env->GetStaticMethodID(clazz, "configATrack",
                               "(IIIII)I");
    if (fields.write_pcm == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MirrorView.writePCM");
        return;
    }

    if (fields.config_audioTrack == NULL)
    {
        jniThrowException(env, "java/lang/RuntimeException", "Can't find MirrorView.writePCM");
        return;
    }

    JNIWritePCM* PCMWriter = new JNIWritePCM(env, thiz, weak_this);

    HI_NativeAAC_SetEnable(env, PCMWriter);
}


//get decode fps
static int native_GetDecodeFPS(JNIEnv* env, jobject object)
{
    return HI_NativeSurface_GetDecodeFPS();
}

//gmethods array
static JNINativeMethod gmethods[] = {
    {"destroyMirrorSurface",             "()V",         (void*) native_DestroySurfaceNative },
    {"startMirrorSurface",    "(Landroid/view/Surface;ILjava/lang/String;)V",  (void*) native_setImgDisplaySurfaceEnable},
    {"getdecodefps",            "()I",            (void*) native_GetDecodeFPS},
    {"startAudio",            "(Ljava/lang/Object;)V",            (void*) native_setPCMCallBackEnable},
    {"configAudio",            "(I)V",            (void*) native_configAudio},
    {"startH264Surface",      "(Landroid/view/Surface;ILjava/lang/String;)V", (void*) native_setH264SurfaceEnable},
    {"destroyH264Surface",    "()V",         (void*) native_DestroyH264Surface},
    {"nativeSetup",           "(Ljava/lang/Object;)V", (void*) native_player_setup}
};

//register native method
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
                             const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    __android_log_print(ANDROID_LOG_INFO, TAG, "Registering %s natives\n", className);
    clazz = env->FindClass(className);
    if (clazz == NULL)
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "Native registration unable to find class '%s'\n", className);
        return -1;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "RegisterNatives failed for '%s'\n", className);
        return -1;
    }
    return 0;
}

//register native method
int register_org_skia_native(JNIEnv* env)
{
    return jniRegisterNativeMethods(env, JAVA_JNI_CALLED_CLASS, gmethods, sizeof(gmethods) / sizeof(gmethods[0]));
}


//jni load
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = JNI_ERR;
    sVm = vm;
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK)
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "GetEnv failed!");
        goto end;
    }

    __android_log_print(ANDROID_LOG_INFO, TAG, "loading . . .");
    if (register_org_skia_native(env) != JNI_OK)
    {
        __android_log_print(ANDROID_LOG_ERROR, TAG, "can't load org_join_skia_SkiaView");
        goto end;
    }

    result = JNI_VERSION_1_4;
    JniEnv = env;
end:
    return result;
}
